package jdos.hardware;

import jdos.cpu.Paging;
import jdos.gui.MainFrame8088;
import jdos.misc.Log;
import jdos.util.IntRef;
import jdos.util.Ptr;

public class Memory {
    
    public static final int MEM_PAGESIZE = 4096;

    //private static final int MEMBASE = 1; // can't use zero
    static private int highwaterMark;
    
    public static int allocate(int size) {
        int result = highwaterMark;
        highwaterMark+=size;
        return result;
    }

    public static void phys_writeb(/*PhysPt*/int addr,/*Bit8u*/int val) {
        RAM.writeb(addr, (short) val);
    }
    public static void phys_writew(/*PhysPt*/int addr,/*Bit16u*/int val){
        RAM.writew(addr, val);
    }
    public static void phys_writed(/*PhysPt*/int addr,/*Bit32u*/int val){
        RAM.writed(addr, val);
    }

    /* The folowing functions are all shortcuts to the above functions using physical addressing */

    public static /*Bit8u*/int real_readb(/*Bit16u*/int seg,/*Bit16u*/int off) {
        return mem_readb((seg<<4)+off);
    }

    public static /*Bit16u*/int real_readw(/*Bit16u*/int seg,/*Bit16u*/int off) {
        return mem_readw((seg<<4)+off);
    }

    public static /*Bit32u*/int real_readd(/*Bit16u*/int seg,/*Bit16u*/int off) {
        return mem_readd((seg << 4) + off);
    }

    public static void real_writeb(/*Bit16u*/int seg,/*Bit16u*/int off,/*Bit8u*/int val) {
        mem_writeb(((seg<<4)+off),val);
    }
    public static void real_writew(/*Bit16u*/int seg,/*Bit16u*/int off,/*Bit16u*/int val) {
        mem_writew(((seg<<4)+off),val);
    }
    public static void real_writed(/*Bit16u*/int seg,/*Bit16u*/int off,/*Bit32u*/int val) {
        mem_writed(((seg<<4)+off),val);
    }

    public static /*Bit16u*/int RealSeg(/*RealPt*/int pt) {
        return (/*Bit16u*/int)((pt>>>16) & 0xFFFF);
    }

    public static /*Bit16u*/int RealOff(/*RealPt*/int pt) {
        return (/*Bit16u*/int)(pt & 0xffff);
    }

    public static /*PhysPt*/int Real2Phys(/*RealPt*/int pt) {
        return (RealSeg(pt)<<4) +RealOff(pt);
    }

    public static /*PhysPt*/int PhysMake(/*Bit16u*/int seg,/*Bit16u*/int off) {
        return (seg<<4)+off;
    }

    public static /*RealPt*/int RealMake(/*Bit16u*/int seg,/*Bit16u*/int off) {
        return (seg<<16)+off;
    }

    public static void RealSetVec(/*Bit8u*/int vec,/*RealPt*/int pt) {
        mem_writed(vec<<2,pt);
    }

    public static void RealSetVec(/*Bit8u*/int vec,/*RealPt*/int pt, IntRef old) {
        old.value = mem_readd(vec << 2);
        mem_writed(vec<<2,pt);
    }

    public static int RealSetVec2(/*Bit8u*/int vec,/*RealPt*/int pt) {
        int ret = mem_readd(vec << 2);
        mem_writed(vec<<2,pt);
        return ret;
    }

    public static /*RealPt*/int RealGetVec(/*Bit8u*/int vec) {
        return mem_readd(vec << 2);
    }

    public static class MemoryBlock {
        public /*Bitu*/int pages;
        /*MemHandle*/int[] mhandles;

        public static class A20 {
            public boolean enabled;
            /*Bit8u*/short controlport;
        }
        public A20 a20 = new A20();
    }
    public static MemoryBlock memory;

    static /*HostPt*/Ptr MemBase;

    static public /*Bitu*/int mem_strlen(/*PhysPt*/int pt) {
        /*Bitu*/int x=0;
        while (x<1024) {
            if (Paging.mem_readb_inline(pt+x)==0) return x;
            x++;
        }
        return 0;		//Hope this doesn't happen
    }

    static public void mem_memcpy(/*PhysPt*/int dest,/*PhysPt*/int src,/*Bitu*/int size) {
        while (size-- !=0) Paging.mem_writeb_inline(dest++,Paging.mem_readb_inline(src++));
    }

    static public void mem_memcpy(byte[] dest, int destOffset, /*PhysPt*/int src,/*Bitu*/int size) {
        while (size-- !=0) dest[destOffset++]=(byte)Paging.mem_readb_inline(src++);
    }

    static public void MEM_BlockRead16u(/*PhysPt*/int pt,int[] data,int offset, /*Bitu*/int size) {
        for (int i=0;i<size;i++) {
            int v1 = Paging.mem_readb_inline(pt++);
            int v2 = Paging.mem_readb_inline(pt++);
            data[i+offset]=((v1 & 0xFF) | ((v2 & 0xFF) << 16));
        }
    }
    static public void MEM_BlockRead(/*PhysPt*/int pt,byte[] data,/*Bitu*/int size) {
        for (int i=0;i<size;i++) {
            data[i]=(byte)(Paging.mem_readb_inline(pt++) & 0xFF);
        }
    }
    static public void MEM_BlockWrite(/*PhysPt*/int pt,byte[] read,/*Bitu*/int size) {
        int i;
        for (i=0;i<size && i<read.length;i++) {
            Paging.mem_writeb_inline(pt++,read[i]);
        }
        for (;i<size;i++) {
            Paging.mem_writeb_inline(pt++,(byte)0);
        }
    }
    static public void MEM_BlockWrite(/*PhysPt*/int pt,String data,/*Bitu*/int size) {
        byte[] read = data.getBytes();
        MEM_BlockWrite(pt, read, size);
    }

    static public void MEM_BlockCopy(/*PhysPt*/int dest,/*PhysPt*/int src,/*Bitu*/int size) {
        mem_memcpy(dest,src,size);
    }

    static public /*Bitu*/int MEM_TotalPages() {
        return memory.pages;
    }

    /*
        A20 line handling,
        Basically maps the 4 pages at the 1mb to 0mb in the default page directory
    */
    public static boolean MEM_A20_Enabled() {
        return memory.a20.enabled;
    }

    public static void MEM_A20_Enable(boolean  enabled) {
        ///*Bitu*/int phys_base=enabled ? (1024/4) : 0;
        //for (/*Bitu*/int i=0;i<16;i++) Paging.PAGING_MapPage((1024/4)+i,phys_base+i);
        memory.a20.enabled=enabled;
    }

    public static /*Bit8u*/int mem_readb(/*PhysPt*/int address) {
        return Paging.mem_readb_inline(address);
    }

    public static /*Bit16u*/int mem_readw(/*PhysPt*/int address) {
        return Paging.mem_readw_inline(address);
    }

    public static /*Bit32u*/int mem_readd(/*PhysPt*/int address) {
        return Paging.mem_readd_inline(address);
    }

    static public void mem_writeb(/*PhysPt*/int address,/*Bit8u*/int val) {
        Paging.mem_writeb_inline(address,(short)val);
    }

    static public void mem_writew(/*PhysPt*/int address,/*Bit16u*/int val) {
        Paging.mem_writew_inline(address,val);
    }

    static public void mem_writed(/*PhysPt*/int address,/*Bit32u*/int val) {
        Paging.mem_writed_inline(address,val);
    }

    static private IoHandler.IO_WriteHandler write_p92 = new IoHandler.IO_WriteHandler() {
        public void call(/*Bitu*/int port, /*Bitu*/int val, /*Bitu*/int iolen) {
            // Bit 0 = system reset (switch back to real mode)
            if ((val&1)!=0) Log.exit("XMS: CPU reset via port 0x92 not supported.");
            memory.a20.controlport = (short)(val & ~2);
            MEM_A20_Enable((val & 2)>0);
        }
    };

    static private IoHandler.IO_ReadHandler read_p92 = new IoHandler.IO_ReadHandler() {
        public /*Bitu*/int call(/*Bitu*/int port, /*Bitu*/int iolen) {
            return memory.a20.controlport | (memory.a20.enabled ? 0x02 : 0);
        }
    };

    private IoHandler.IO_ReadHandleObject ReadHandler = new IoHandler.IO_ReadHandleObject();
    private IoHandler.IO_WriteHandleObject WriteHandler = new IoHandler.IO_WriteHandleObject();

    public Memory() {
        /*Bitu*/int i;

			/* Setup the Physical Page Links */

            memory = new MemoryBlock();
                highwaterMark = MainFrame8088.memsize;

                RAM.alloc(highwaterMark);
            memory.pages = (highwaterMark)/4096;
            /* Allocate the data for the different page information blocks */
            memory.mhandles=new /*MemHandle*/int[memory.pages];
            for (i = 0;i < memory.pages;i++) {
                memory.mhandles[i] = 0;				//Set to 0 for memory allocation
            }
            /* Reset some links */
            // A20 Line - PS/2 system control port A
            WriteHandler.Install(0x92,write_p92,IoHandler.IO_MB);
            ReadHandler.Install(0x92,read_p92,IoHandler.IO_MB);
            MEM_A20_Enable(false);
    }

    static Memory test;
    public static void MEM_Init() {
        test = new Memory();
    }
}
